// CommandLineParser.cs: Contributed by Chris Sells [csells@sellsbrothers.com]
// A command line parser class -- see the sample for usage
#region Copyright  2002 The Genghis Group
/*
 * This software is provided 'as-is', without any express or implied warranty.
 * In no event will the authors be held liable for any damages arising from the
 * use of this software.
 *
 * Permission is granted to anyone to use this software for any purpose,
 * including commercial applications, subject to the following restrictions:
 *
 * 1. The origin of this software must not be misrepresented; you must not claim
 * that you wrote the original software. If you use this software in a product,
 * an acknowledgment in the product documentation is required, as shown here:
 *
 * Portions copyright  2002 The Genghis Group (http://www.genghisgroup.com/).
 *
 * 2. No substantial portion of the source code of this library may be redistributed
 * without the express written permission of the copyright holders, where
 * "substantial" is defined as enough code to be recognizably from this library.
*/
#endregion
#region Features
/*
 * -Parsing command line args from an array of strings or a file
 * -Simple flags and flag/arg pairs
 * -Typed args, both params and flags
 * -Building usages on the fly
 * -Command line args can be read via @argfile
 * -Automatically generates the banner logo from the version attributes
 * -Case insensitive flag comparisions by default, case sensitive as an option
*/
#endregion
#region Limitations
/*
 * -No reporting of default values in usage.
 * -Flags should support +/- at the end to turn on/off
 * -Requires flags to be space separated, e.g.
 *  /efg is a single flag, even if e, f and g are defined and efg isn't
 * -Requires flag and value to be space separated, e.g.
 * /fconfig.sys is treated as a single flag, even if f is defined to take
 *  a value and fconfig.sys isn't defined
 * -No support for separating flag from value via color or equals sign,
 *  as is fairly common
 * -Need better formatting to pad flag/param names that are larger than
 *  16 characters and to support multi-line descriptions.
 * -There is no equivilent (yet) of RestrictedValueArg, FileNameValue or
 *  PairValue from the unmanaged code.
*/
#endregion
#region History
/*
 * 4/28/01:
 * -Moved the CLP to the Genghis namespace from the Genghis.General namespace.
 *
 * 3/27/01:
 * -Initial port from unmanaged C++ (http://www.sellsbrothers.com/tools/commandlineparser.zip)
*/
#endregion

using System;
using System.IO;
using System.Text;
using System.Diagnostics;
using System.ComponentModel;
using System.Reflection;
using System.Collections;
using System.Runtime.InteropServices;

namespace GenericServiceInstaller
  //namespace Genghis
{
  public class CommandLineParser
  {
    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]
    public class ParserUsageAttribute : Attribute
    {
      public ParserUsageAttribute(string description) { Description = description; }
      public string   Description;
      public string   PreferredPrefix = "/";
      public bool     AllowArgumentFile = true;
    }

    [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
    public abstract class UsageAttribute : Attribute
    {
      public UsageAttribute(string description) { Description = description; }

      public string   Description = "";
      public string   Name = "";
      public bool     IgnoreCase = true;
      public bool     Optional = true;
      public string   ValueName = "value";
      public bool     MatchPosition = false;

      // NOTE: Using two alternates instead of an array because
      // array attributes are not CLS compliant
      public string   AlternateName1 = "";
      public string   AlternateName2 = "";

      protected bool  found = false;

      protected bool Flag
      {
        get { return !MatchPosition; }
      }

      public bool Matches(string name, string defaultName)
      {
        //Debug.Assert(defaultName.Length > 0);
        return (string.Compare(name, (Name.Length == 0 ? defaultName : Name), IgnoreCase) == 0) ||
          (string.Compare(name, AlternateName1, IgnoreCase) == 0) ||
          (string.Compare(name, AlternateName2, IgnoreCase) == 0);
      }

      protected internal virtual bool IsFound
      {
        get { return (Optional ? true : found); }
      }

      protected internal abstract bool ExpectsValue
      {
        get;
      }

      protected internal abstract void ConsumeValue(string s, object container, MemberInfo info);
      protected internal abstract string GetShortUsage(string prefix, string defaultName);
      protected internal abstract string GetLongUsage(string prefix, string defaultName);

      protected void ConsumeValueHelper(string s, object container, MemberInfo info)
      {
        // TODO: Read value from string into array (efficiently?)
        // TODO: Indexed properties?
        try
        {
          PropertyInfo    prop = info as PropertyInfo;
          FieldInfo       field = info as FieldInfo;
          //Debug.Assert((prop != null) || (field != null));

          if( field != null )
          {
            // Collection field
            IList   list = field.GetValue(container) as IList;
            if( list != null )
            {
              list.Add(s);
            }
              // Simple field
            else
            {
              field.SetValue(container, ReadFromString(s, field.FieldType));
            }
          }
            // Property
          else
          {
            //Debug.Assert(prop != null);
            prop.SetValue(container, ReadFromString(s, prop.PropertyType), null);
          }
        }
        catch( TargetInvocationException e )
        {
          throw e.InnerException;
        }
      }

      // Normally, prefix == "/"
      // defaultName is the name of the field or property obtained via reflection
      protected string GetShortUsageHelper(string prefix, string defaultName, string valueName)
      {
        StringBuilder   usage = new StringBuilder();
        if( Optional ) usage.Append("[");
        usage.Append(prefix);

        // If there's no prefix, assume a param instead of a flag,
        // and wrap the name (great for names with spaces, e.g. "min value" in [/minX <min value>])
        bool    wrapName = prefix.Length == 0;
        if( wrapName && !Optional ) usage.Append("<");

        //Debug.Assert(defaultName.Length > 0);
        if( Name.Length != 0 ) usage.Append(Name);
        else usage.Append(defaultName);
        if( AlternateName1.Length > 0 ) usage.Append("|").Append(AlternateName1);
        if( AlternateName2.Length > 0 ) usage.Append("|").Append(AlternateName2);

        if( valueName.Length != 0 ) usage.Append(" <").Append(ValueName).Append(">");
        if( wrapName && !Optional ) usage.Append(">");
        if( Optional ) usage.Append("]");
        return usage.ToString();
      }

      #region Long usage on three lines (scales but takes up too much room)
      /*
            protected string GetLongUsageHelper(string prefix, string defaultName, string valueName)
            {
                StringBuilder   usage = new StringBuilder();

                usage.Append(prefix);

                //Debug.Assert(defaultName.Length > 0);
                if( Name.Length != 0 ) usage.Append(Name);
                else usage.Append(defaultName);

                if( valueName.Length != 0 ) usage.Append(" <").Append(ValueName).Append(">");

                // TODO: Show default value

                usage.Append(":").Append(Environment.NewLine);
                usage.Append(Description).Append(Environment.NewLine);
                return usage.ToString();
            }
            */
      #endregion

      #region Long usage all in one line (doesn't scale)
      protected string GetLongUsageHelper(string prefix, string defaultName, string valueName)
      {
        StringBuilder   usage = new StringBuilder();

        if( Description.Length == 0 ) return "";

        usage.Append(prefix);

        //Debug.Assert(defaultName.Length > 0);
        if( Name.Length != 0 ) usage.Append(Name);
        else usage.Append(defaultName);

        if( valueName.Length != 0 ) usage.Append(" <").Append(ValueName).Append(">");

        // TODO: Show default value

        // Line all descriptions up on two tabs
        if( usage.Length < 8 ) usage.Append('\t');
        usage.Append('\t').Append(Description);

        return usage.ToString();
      }
      #endregion

      protected object ReadFromString(string s, Type type)
      {
        TypeConverter   converter = TypeDescriptor.GetConverter(type);
        return converter.ConvertFromString(s);
      }
    }

    // Presence on the commandline, e.g. /foo
    [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
    public class FlagUsageAttribute : UsageAttribute
    {
      public FlagUsageAttribute(string description) : base(description) {}

      protected internal override bool ExpectsValue
      {
        get { return false; }
      }

      protected internal override void ConsumeValue(string s, object container, MemberInfo info)
      {
        // TODO: Handle true/false to handle trailing +/- on a flag
        //Debug.Assert(s.ToLower() == "true", "Flags only consume boolean values");

        ConsumeValueHelper(s, container, info);
        found = true;
      }

      protected internal override string GetShortUsage(string prefix, string defaultName)
      {
        return GetShortUsageHelper(prefix, defaultName, "");
      }

      protected internal override string GetLongUsage(string prefix, string defaultName)
      {
        return GetLongUsageHelper(prefix, defaultName, "");
      }
    }

    // On the commandline with an associated value,
    // e.g. /foo "fooness" (MatchPosition = false) or "fooness" (MatchPosition = true)
    [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
    public class ValueUsageAttribute : UsageAttribute
    {
      public ValueUsageAttribute(string description) : base(description) {}

      protected internal override bool ExpectsValue
      {
        get { return true; }
      }

      protected internal override void ConsumeValue(string s, object container, MemberInfo info)
      {
        ConsumeValueHelper(s, container, info);
        found = true;
      }

      protected internal override string GetShortUsage(string prefix, string defaultName)
      {
        bool    flag = (prefix != null) && (prefix.Length != 0);
        return GetShortUsageHelper(prefix, defaultName, Flag ? ValueName : "");
      }

      protected internal override string GetLongUsage(string prefix, string defaultName)
      {
        // TODO: If we're filling an array or collection, append "..." to ValueName
        // return UsageHelper(sPrefix, true, bFlag ? __T("value") : __T("")) + __T("...");
        return GetLongUsageHelper(prefix, defaultName, Flag ? ValueName : "");
      }
    }

    // Skip variables marked NoUsage. By default, all fields and properties
    // are assumed to be part of the usage. This makes it easy to get
    // started with this class, i.e. to hand an object full of fields to
    // the parser without any special attributes and have it work.
    // NOTE: Deriving from UsageAttribute makes enumerating
    // our attributes easier, as they all have a common base
    [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
    public class NoUsageAttribute : UsageAttribute
    {
      public NoUsageAttribute() : base(null) {}

      protected internal override bool ExpectsValue
      {
        get { return false; }
      }

      protected internal override void ConsumeValue(string s, object container, MemberInfo info) {}
      protected internal override string GetShortUsage(string prefix, string defaultName) { return ""; }
      protected internal override string GetLongUsage(string prefix, string defaultName) { return ""; }
    }

    public class UsageException : ApplicationException
    {
      public UsageException(string arg, string error) : base(error) { this.arg = arg; }
      protected string arg = "";
      public string Argument
      {
        get { return arg; }
      }

      public override string Message
      {
        get { return arg + ": " + base.Message; }
      }
    }

    protected class MemberInfoUsage
    {
      public MemberInfo       info;
      public UsageAttribute   usage;

      public MemberInfoUsage(MemberInfo info, UsageAttribute usage)
      {
        this.info = info;
        this.usage = usage;
      }
    }

    
    [NoUsage]
    protected MemberInfoUsage[] members;

    [NoUsage]
    private Hashtable _additionalArguments = new Hashtable();

    protected MemberInfoUsage[] GetMembers()
    {
      // Return cached members
      if( members != null ) return members;

      // Cache members
      ArrayList   memberList = new ArrayList();

      // TODO: Parse base class first to get /v and /h shown first
      BindingFlags    flags = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public;
      foreach( MemberInfo info in GetType().GetMembers(flags) )
      {
        // Only doing fields and properties
        if( (info.MemberType != MemberTypes.Field) && (info.MemberType != MemberTypes.Property) ) continue;

        // Skip variables w/o usage
        object[]        attribs = info.GetCustomAttributes(typeof(UsageAttribute), true);
        UsageAttribute  usage = (UsageAttribute)(attribs.Length != 0 ? attribs[0] : null);
        if( usage is NoUsageAttribute ) continue;

        // Default settings with no attribute
        if( usage == null )
        {
          PropertyInfo    prop = info as PropertyInfo;
          FieldInfo       field = info as FieldInfo;
          //Debug.Assert((prop != null) || (field != null));

          // If the type is bool, it's probably just a flag
          if( ((prop != null) && (prop.PropertyType == typeof(bool))) ||
            ((field != null) && (field.FieldType == typeof(bool))) )
          {
            usage = new FlagUsageAttribute(info.Name);
          }
            // If the type is not a bool, it probably also have a value
          else
          {
            usage = new ValueUsageAttribute(info.Name);
          }
        }

        memberList.Add(new MemberInfoUsage(info, usage));
      }

      members = (MemberInfoUsage[])memberList.ToArray(typeof(MemberInfoUsage));
      return members;
    }

    public string GetUsage()
    {
      return GetUsage("");
    }

    public string GetUsage(string err)
    {
      StringBuilder   usage = new StringBuilder();

      // Logo
      string  logo = GetLogo();
      if( logo.Length != 0 )
      {
        usage.Append(logo).Append(Environment.NewLine);
      }

      // Parser prefs, e.g. preferred prefix
      object[]                attribs = GetType().GetCustomAttributes(typeof(ParserUsageAttribute), true);
      ParserUsageAttribute    parser = (ParserUsageAttribute)(attribs.Length != 0 ? attribs[0] : null);
      string                  preferredPrefix = (parser != null ? parser.PreferredPrefix : "/");
      bool                    allowArgFile = (parser != null ? parser.AllowArgumentFile : true);

      // Error string
      if( err.Length != 0 )
      {
        usage.Append(err).Append(Environment.NewLine).Append(Environment.NewLine);
      }

      // Short (name and value name only)
      StringBuilder   shortUsage = new StringBuilder();
      shortUsage.Append("Usage: ").Append(GetModuleName()).Append(" ");

      // Long (name and description only)
      StringBuilder   longUsage = new StringBuilder();

      if( allowArgFile )
      {
        shortUsage.Append("[@argfile]");
        longUsage.Append("@argfile\tRead arguments from a file.");
      }

      foreach( MemberInfoUsage member in GetMembers() )
      {
        // NOTE: When matching by position, only the value will be present
        // on the commandline, e.g. "fooness"
        string  prefix = (member.usage.MatchPosition ? "" : preferredPrefix);

        shortUsage.Append(" ").Append(member.usage.GetShortUsage(prefix, member.info.Name));
        longUsage.Append(Environment.NewLine).Append(member.usage.GetLongUsage(prefix, member.info.Name));
      }

      usage.Append(shortUsage).Append(Environment.NewLine).Append(Environment.NewLine).Append(longUsage).Append(Environment.NewLine);
      return usage.ToString();
    }

    MemberInfoUsage FindFlag(string name)
    {
      foreach( MemberInfoUsage member in GetMembers() )
      {
        if( !member.usage.MatchPosition && member.usage.Matches(name, member.info.Name) )
        {
          return member;
        }
      }

      return null;
    }

    MemberInfoUsage GetNextParam()
    {
      foreach( MemberInfoUsage info in GetMembers() )
      {
        if( !info.usage.IsFound ) return info;
      }

      return null;
    }

    public void Parse(string commandLine)
    {
      ArrayList   args = new ArrayList();
      string      arg = "";
      bool        inQuotes = false;

      for( int i = 0; i != commandLine.Length; ++i )
      {
        char    c = commandLine[i];

        if( c == '"' )
        {
          inQuotes = !inQuotes;
          continue;
        }

        if( Char.IsWhiteSpace(c) && !inQuotes )
        {
          if( arg.Length != 0 )
          {
            args.Add(arg);
            arg = "";
          }
          continue;
        }

        arg += c;
      }

      // Add last arg and parse the list
      if( arg.Length != 0 ) args.Add(arg);
      Parse((string[])args.ToArray(typeof(string)));
    }

    void Parse(string[] args)
    {
      object[]                attribs = GetType().GetCustomAttributes(typeof(ParserUsageAttribute), true);
      ParserUsageAttribute    parser = (ParserUsageAttribute)(attribs.Length != 0 ? attribs[0] : null);
      bool                    allowArgFile = (parser != null ? parser.AllowArgumentFile : true);
      MemberInfoUsage[]       members = GetMembers();

      for( int i = 0; i != args.Length; ++i )
      {
        string  arg = args[i];
        //Debug.WriteLine("Processing arg: " + arg);

        MemberInfoUsage member = null;
        bool            isFlag = false;
        string argName = String.Empty;

        // It's a flag
        if( (arg.Length > 1) && ((arg[0] == '/') || (arg[0] == '-')) )
        {

          // Find the argument by name
          int index = arg.IndexOfAny(new char[] { ':', '=' });
          if(index > 0)
          {
            argName = arg.Substring(1, index - 1);
            member = FindFlag(argName);
          }
          else
          {
            argName = arg.Substring(1);
            member = FindFlag(argName);
          }

          isFlag = true;
        }
        else if( (arg.Length > 1) && (arg[0] == '@') && allowArgFile )
        {
          // It's a file name to process parameters from
          ParseFromFile(arg.Substring(1));
          continue;
        }
        else
        {
          // It's a parameter, find the argument by offset
          member = GetNextParam();
        }

        if(member==null) 
        {
          //throw new UsageException(arg, "Unrecognized argument");

          if(++i<args.Length)
            this._additionalArguments.Add(argName, args[i]);

        }
        else
        {

          // Argument with a value, e.g. /foo bar
          if( member.usage.ExpectsValue )
          {
            if( isFlag && (++i == args.Length) ) 
              throw new UsageException(arg, "Argument expects a parameter");

            string  value = args[i];
            member.usage.ConsumeValue(value, this, member.info);

          }
          else
          {
            // Argument w/o a value, e.g. /foo
            member.usage.ConsumeValue("true", this, member.info);
          }

        }

      }

      // Check for missing required arguments
      foreach( MemberInfoUsage member in members )
      {
        if( !member.usage.Optional && !member.usage.IsFound )
        {
          throw new UsageException(member.info.Name, "Required argument not found");
        }
      }

    }

    /// <summary>
    /// 
    /// </summary>
    [NoUsage()]
    public IDictionary AdditionalArguments
    {
      get
      {
        return this._additionalArguments;
      }
    }

    /// <summary>
    /// Indexer for AdditionalArguments
    /// </summary>
    [NoUsage()]
    public string this[string name]
    {
      get
      {
        return (string)this._additionalArguments[name];
      }
    }

    // Set (and auto-reset) current working directory
    // We do this so that any file names read out of the file
    // can be relative to the file, not where we're running
    // the app from. We're using the system managed working directory
    // as a "context" for the individual values, i.e. FileNameValue,
    // to be able to compute a complete file name. This is potentially
    // dangerous as the cwd is set per process, not per thread, but
    // since command lines are typically processed before threads are
    // fired off, we should be safe. It saves us from having to pass
    // a virtual cwd to all values as they're parsed.
    // TODO: This doesn't really work if the file/directory names
    // are just strings, since but the time they're turned into
    // full path names, the CWD has already been reset...
    protected class CurrentDir : IDisposable
    {
      public CurrentDir(string newDir)
      {
        oldDir = Environment.CurrentDirectory;
        Environment.CurrentDirectory = newDir;
      }

      #region Implementation of IDisposable
      public void Dispose()
      {
        Environment.CurrentDirectory = oldDir;
      }
      #endregion

      private string oldDir = "";
    }

    void ParseFromFile(string fileName)
    {
      // Check if file exists
      if( !(new FileInfo(fileName)).Exists )
      {
        throw new UsageException("argfile", fileName + " not found");
      }

      // Point current directory at the input file
      string  argDir = Path.GetDirectoryName(Path.GetFullPath(fileName));
      using( CurrentDir   cd = new CurrentDir(argDir) )
      using( StreamReader reader = new StreamReader(fileName) )
      {
        Parse(reader.ReadToEnd());
      }
    }

    public virtual string GetLogo()
    {
      // TODO: Refactor this ugly code!
      StringBuilder   logo = new StringBuilder();
      string          nl = Environment.NewLine;
      object[]        attribs = null;

      Assembly        assem = Assembly.GetEntryAssembly();
      AssemblyName    assemName = assem.GetName();

      // Title: try AssemblyTitle first, use module name if AssemblyTitle is missing
      attribs = assem.GetCustomAttributes(typeof(AssemblyTitleAttribute), true);
      string  title = (attribs.Length != 0 ? ((AssemblyTitleAttribute)attribs[0]).Title : "");
      if( title.Length == 0 ) title = GetModuleName();

      // Version
      string  version = assem.GetName().Version.ToString();

      // Copyright
      attribs = assem.GetCustomAttributes(typeof(AssemblyCopyrightAttribute), true);
      string  copyright = (attribs.Length != 0 ? ((AssemblyCopyrightAttribute)attribs[0]).Copyright : "");

      // Description: Try ParserUsage.Description first, use AssemblyDescription otherwise
      string                  description = "";
      attribs = GetType().GetCustomAttributes(typeof(ParserUsageAttribute), true);
      ParserUsageAttribute    parser = (ParserUsageAttribute)(attribs.Length != 0 ? attribs[0] : null);
      if( (parser != null) && (parser.Description != null) && (parser.Description.Length != 0) )
      {
        description = parser.Description;
      }
      else
      {
        attribs = assem.GetCustomAttributes(typeof(AssemblyDescriptionAttribute), true);
        description = (attribs.Length != 0 ? ((AssemblyDescriptionAttribute)attribs[0]).Description : "");
      }

      // Layout
      logo.Append(title).Append(" v").Append(version).Append(nl);

      if( copyright.Length != 0 )
      {
        logo.Append(copyright).Append(nl);
      }

      if( (description != null) && (description.Length != 0) )
      {
        logo.Append(description).Append(nl);
      }

      return logo.ToString();
    }

    public enum ImageSubsystem
    {
      Native  = 0x0001,   // Currently not supported
      GUI     = 0x0002,   // Windows EXE or DLL
      CUI     = 0x0003,   // Console EXE
    }

    // This code heavily inspired by Brent Rector [brent@wiseowl.com]. Thanks, Brent!
    public static ImageSubsystem GetImageSubsystem(string filename)
    {
      using( FileStream   fs = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read) )
      using( BinaryReader reader = new BinaryReader(fs) )
      {
        // Seek to the beginning of the PE signature offset
        reader.BaseStream.Seek (0x3c, System.IO.SeekOrigin.Begin);

        // Get the offset to the PE signature
        uint e_lfanew = reader.ReadUInt32();

        // Seek to the beginning of the PE Header
        reader.BaseStream.Seek (e_lfanew, SeekOrigin.Begin);

        // Read the PE signature
        uint    PE_SIGNATURE = 0x00004550;
        uint    PESignature = reader.ReadUInt32();
        if( PESignature != PE_SIGNATURE ) throw new BadImageFormatException("Bad PE signature: " + filename);

        // Seek past the file header
        reader.BaseStream.Seek(0x14, SeekOrigin.Current);

        // Seek to the subsystem in the optional header
        reader.BaseStream.Seek(0x44, SeekOrigin.Current);

        switch( reader.ReadUInt16() )
        {
          case (ushort)ImageSubsystem.Native: return ImageSubsystem.Native;
          case (ushort)ImageSubsystem.GUI:    return ImageSubsystem.GUI;
          case (ushort)ImageSubsystem.CUI:    return ImageSubsystem.CUI;
          default: throw new BadImageFormatException("Invalid subsystem: " + filename);
        }
      }
    }

    public static bool IsConsole()
    {
      return GetImageSubsystem(Assembly.GetEntryAssembly().Location) == ImageSubsystem.CUI;
    }

    #region P/Invoke implemention of IsConsole
    /*
        struct SHFILEINFO
        {
            UIntPtr hIcon;          // out: icon
            int     iIcon;          // out: icon index
            uint    dwAttributes;   // out: SFGAO_ flags
            [ MarshalAs( UnmanagedType.ByValArray, SizeConst=260 )]
            char[]  szDisplayName;  // out: display name (or path)
            [ MarshalAs( UnmanagedType.ByValArray, SizeConst=80 )]
            char[]  szTypeName;     // out: type name
        }

        [DllImport("shell32.dll")]
        static extern IntPtr SHGetFileInfo(string pszPath, uint dwFileAttributes, out SHFILEINFO psfi, uint cbFileInfo, uint uFlags);

        static ushort LOWORD(uint u)
        {
            return ((ushort)(u & 0xffff));
        }

        static ushort HIWORD(uint u)
        {
            return ((ushort)(u >> 16));
        }

        public static bool IsConsole()
        {
            SHFILEINFO  sfi = new SHFILEINFO();
            uint        SHGFI_EXETYPE = 0x000002000;
            uint        IMAGE_NT_SIGNATURE = 0x00004550;    // NOTE: Not on the Mac, though...
            uint        exeType = (uint)SHGetFileInfo(Assembly.GetEntryAssembly().Location, 0, out sfi, (uint)Marshal.SizeOf(typeof(SHFILEINFO)), SHGFI_EXETYPE);
            return (LOWORD(exeType) == IMAGE_NT_SIGNATURE) && (HIWORD(exeType) == 0);
        }
        */
    #endregion

    public static string GetModuleName()
    {
      return Path.GetFileName(Assembly.GetEntryAssembly().Location);
    }

    void Show(string s)
    {
      if( IsConsole() )
      {
        // Always send usage to stdout so it's easy to capture the output
        Console.WriteLine(s);
      }
      else
      {
        try
        {
          // FIX: Don't require project to reference System.Windows.Forms.dll
          Assembly    assem = Assembly.LoadWithPartialName("System.Windows.Forms");
          Type        type = assem.GetType("System.Windows.Forms.MessageBox");
          MethodInfo  method = type.GetMethod("Show", new Type[] { typeof(string), typeof(string) });
          method.Invoke(null, new Object[] { s, GetModuleName() });
          //System.Windows.Forms.MessageBox.Show(s, GetModuleName());
        }
        catch {}
      }
    }

    bool Continue(string err)
    {
      if( version )
      {
        Show(GetLogo());
        return false;
      }

      if( (err.Length > 0) || help )
      {
        Show(GetUsage(help ? "" : err));
        return false;
      }

      return true;
    }

    public bool ParseAndContinue(string[] args)
    {
      string  err = "";
      try { Parse(args); }
      catch( Exception e ) { err = e.Message; }
      return Continue(err);
    }

    [FlagUsage("Show usage.", AlternateName1 = "?", AlternateName2 = "h")]
    public bool help;

    [FlagUsage("Show version.", AlternateName1 = "v")]
    public bool version;
  }

}
